﻿/*
 *	Copyright (c) 2009 Queensland University of Technology. All rights reserved.
 *	The QUT Bioinformatics Collection is open source software released under the 
 *	Microsoft Public License (Ms-PL): http://www.microsoft.com/opensource/licenses.mspx.
 */
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ServiceModel;
using System.ComponentModel;
using QUT.Bio.TagService;
using QUT.Bio.Util;

namespace QUT.Bio.Tags {
	public static class Tags {

		//----- Variables ------------------------------------------------------------------

		public delegate string ObjectIdYielder( object x );

		public delegate object ObjectLookup( Type type, string id );

		public delegate SimpleProgressWindow ProgressWindowCreator( string text, double width, double height );

		public delegate void LogWriter( string format, params object[] args );

		private static Dictionary<Type, string> objectNamespaces = new Dictionary<Type, string>();

		private static Dictionary<string, string> ontologyNamespacePredicates = new Dictionary<string, string> {
			{ "keywords", "http://purl.org/dc/elements/1.1/title" },
			{ "go", "http://bio2rdf.org/ns/go#name" }
		};

		private static string authenticatedUserName;

		private static string[] sortedOntologyNamespaces = { "go", "keywords" };

		private static Dictionary<string, List<CategorisedRdfTagTemplate>> tagTemplates = new Dictionary<string, List<CategorisedRdfTagTemplate>>();

		private static Dictionary<Uri, List<WrappedRdfTag>> uriTags = new Dictionary<Uri, List<WrappedRdfTag>>();
		private static Dictionary<string, List<Uri>> tagUris = new Dictionary<string, List<Uri>>();
		private static List<RdfTag> allTagsAccessibleToUser = new List<RdfTag>();

		public const string PredicateUsefulForTaggingType = "http://purl.org/net/terms#UsefulForTaggingType";
		public const string PredicateTermMeans = "http://purl.org/net/terms#termMeans";

		public static event Action WhenTagsLoaded;
		public static event Action WhenOntologyTemplatesLoaded;

		private static WrappedRdfTag[] emptyTags = { };

		private static string webServiceAddress = "http://www.mquter.qut.edu.au/SilverMapBeta/RdfTagService.asmx";

		private static ProgressWindowCreator createProgressWindow;
		private static double progressWidth = 320;
		private static double progressHeight = 120;

		//----- Constructors ----------------------------------------------------------------

		public static void Initialize() {
			RdfTagServiceSoapClient sc = GetSoapClient();

			sc.GetLoggedInUserUriCompleted += delegate( object sender, GetLoggedInUserUriCompletedEventArgs args ) {
				if ( AllOk( "GetLoggedInUserUri", args ) ) {
					authenticatedUserName = args.Result;
				}

				LoadAllTags();
			};

			sc.GetLoggedInUserUriAsync();
		}

		//----- Properties ------------------------------------------------------------------

		public static bool AllTagsLoaded {
			get;
			private set;
		}

		public static Dictionary<string, string> OntologyNamespacePredicates {
			get {
				return ontologyNamespacePredicates;
			}
		}

		public static IEnumerable<string> SortedOntologyNamespaces {
			get {
				return sortedOntologyNamespaces;
			}
		}

		public static Dictionary<string, List<CategorisedRdfTagTemplate>> TagTemplates {
			get {
				return tagTemplates;
			}
		}

		public static bool OntologyTemplatesLoaded {
			get;
			private set;
		}

		public static IEnumerable<RdfTag> AllTagsAccessibleToUser {
			get {
				return allTagsAccessibleToUser;
			}
		}

		public static Dictionary<Type, string> ObjectNamespaces {
			get {
				return objectNamespaces;
			}
		}

		public static ObjectIdYielder GetIdOfObject {
			get;
			set;
		}

		public static LogWriter WriteLine {
			get;
			set;
		}

		public static ObjectLookup GetObjectHavingId {
			get;
			set;
		}

		public static string WebServiceAddress {
			get {
				return webServiceAddress;
			}
			set {
				webServiceAddress = value;
			}
		}

		public static ProgressWindowCreator CreateProgressWindow {
			get {
				return createProgressWindow;
			}
			set {
				createProgressWindow = value;
			}
		}

		private static double ProgressWidth { 
			get {
				return progressWidth;
			}
			set {
				progressWidth = value;
			}
 }
		
		private static double ProgressHeight {
			get {
				return progressHeight;
			}
			set {
				progressHeight = value;
			}
		}
		//----- Methods ---------------------------------------------------------------------

		private static RdfTagServiceSoapClient GetSoapClient() {
			RdfTagServiceSoapClient res = new RdfTagServiceSoapClient(
				new BasicHttpBinding() {
					MaxReceivedMessageSize = Int32.MaxValue,
					MaxBufferSize = Int32.MaxValue
				},
				new EndpointAddress( webServiceAddress )
			);
			res.InnerChannel.OperationTimeout = new TimeSpan( 0, 2, 0 );
			return res;
		}

		public static Uri GetUri( object x ) {
			string stem = string.Format( "http://bio2rdf.org/{0}:", objectNamespaces[x.GetType()] );

			return new Uri( string.Format( "{0}{1}", stem, GetIdOfObject( x ) ) );
		}

		private static void LoadAllTags() {
			WriteLine( "{0}: LoadAllTags starting", DateTime.Now );
			RdfTagServiceSoapClient sc = GetSoapClient();

			SimpleProgressWindow progress = ShowProgressWindow( "Loading tags..." );

			sc.GetTagsAccessibleToLoggedInUserCompleted += delegate( object sender, GetTagsAccessibleToLoggedInUserCompletedEventArgs args ) {
				HideProgressWindow( progress );

				WriteLine( "{0}: LoadAllTags data received", DateTime.Now );
				if ( AllOk( "LoadAllTags", args ) ) {

					allTagsAccessibleToUser.Clear();
					allTagsAccessibleToUser.AddRange( args.Result );

					foreach ( RdfTag rdfTag in allTagsAccessibleToUser ) {
						// Status.WriteLine( "Tag: {0} -> {1}", rdfTag.TagLabelName, rdfTag.TagSubjectUri );
						Type t = null;

						foreach ( KeyValuePair<Type, string> p in objectNamespaces ) {
							if ( rdfTag.TagSubjectUri.IndexOf( p.Value ) >= 0 ) {
								t = p.Key;
								break;
							}
						}

						if ( t != null ) {
							AddTag( rdfTag );
						}
					}

					AllTagsLoaded = true;

					CreateUserTagTemplates();
					CreateOntologyTagTemplates();

					if ( WhenTagsLoaded != null ) {
						WhenTagsLoaded();
					}
				}
			};

			sc.GetTagsAccessibleToLoggedInUserAsync( int.MaxValue, 0, true );
		}

		private static void AddTag( RdfTag rdfTag ) {
			Uri subjectUri = new Uri( rdfTag.TagSubjectUri );
			string tagLabel = rdfTag.TagLabelName;

			List<WrappedRdfTag> tags = null;

			if ( uriTags.ContainsKey( subjectUri ) ) {
				tags = uriTags[subjectUri];
			}
			else {
				tags = new List<WrappedRdfTag>();
				uriTags[subjectUri] = tags;
			}

			tags.Add( new WrappedRdfTag( rdfTag ) );

			List<Uri> subjectUris = null;

			if ( tagUris.ContainsKey( tagLabel ) ) {
				subjectUris = tagUris[tagLabel];
			}
			else {
				subjectUris = new List<Uri>();
				tagUris[tagLabel] = subjectUris;
			}

			if ( !subjectUris.Contains( subjectUri ) ) {
				subjectUris.Add( subjectUri );
			}
		}

		private static bool AllOk( string methodName, AsyncCompletedEventArgs args ) {
			if ( args.Cancelled ) {
				WriteLine( "{0}: cancelled.", methodName );
				return false;
			}
			else if ( args.Error != null ) {
				WriteLine( "{0}: error:\n{1}", methodName, args.Error );
				return false;
			}
			else {
				return true;
			}
		}

		public static StringCounter[] GetTagCounts( Type t, bool isAlreadyLoaded ) {
			List<StringCounter> c = new List<StringCounter>();

			foreach ( KeyValuePair<string, List<Uri>> p in tagUris ) {
				int count = 0;

				string tagSpace = Tags.objectNamespaces[t];

				foreach ( Uri subjectUri in p.Value ) {
					try {
						string[] parts = subjectUri.AbsolutePath.Split( '/', ':' );

						if ( parts[parts.Length - 2] == tagSpace ) {
							bool wanted = false;

							if ( isAlreadyLoaded ) {
								wanted = GetObjectHavingId( t, parts[parts.Length - 1] ) != null;
							}
							else {
								wanted = true;
							}

							if ( wanted ) {
								count++;
							}
						}
					}
					catch ( Exception e ) {
						WriteLine( "Zut alors!!!\n{0}", e );
					}
				}

				c.Add( new StringCounter( p.Key, count ) );
			}

			return c.ToArray();
		}

		private static GroupInformation[] emptyGroups = { };
		
		private static SimpleProgressWindow ShowProgressWindow ( string title ) {
			return createProgressWindow == null ? null : createProgressWindow( title, progressWidth, progressHeight );
		}
		
		private static void HideProgressWindow( SimpleProgressWindow window ) {
			if ( window != null ) {
					window.Container = null;
			}
		}
		
		public static void CreateTags(
			IEnumerable<CategorisedRdfTagTemplate> templates,
			object[] subjects,
			Action postProcess
		) {
			string methodName = "CreateTags";
			WriteLine( "{1}: {0} starting", methodName, DateTime.Now );

			int subjectsRemaining = subjects.Length;
			
			RdfTagServiceSoapClient sc = GetSoapClient();

			SimpleProgressWindow progress = ShowProgressWindow( "Setting tags..." );

			sc.CreateTagsOnItemsCompleted += delegate( object sender, CreateTagsOnItemsCompletedEventArgs args ) {
				HideProgressWindow( progress );

				WriteLine( "{1}: {0} starting", methodName, DateTime.Now );

				if ( AllOk( methodName, args ) ) {
					lock ( allTagsAccessibleToUser ) {
						allTagsAccessibleToUser.AddRange( args.Result );

						foreach ( RdfTag tagStruct in args.Result ) {
							AddTag( tagStruct );
						}

						subjectsRemaining--;
					}

					if ( subjectsRemaining == 0 && postProcess != null ) {
						postProcess();
					}
				}
			};

			foreach ( object subject in subjects ) {
				IEnumerable<WrappedRdfTag> existingTags = GetTags( subject );
				IEnumerable<RdfTag> tagTemplates = from t in templates
												   where !existingTags.Contains( t )
												   select t.Tag;

				if ( tagTemplates.Any() ) {
					ArrayOfString subjectUris = new ArrayOfString() { GetUri( subject ).ToString() };
					sc.CreateTagsOnItemsAsync( tagTemplates.ToArray(), subjectUris, emptyGroups );
				}
				else {
					lock ( allTagsAccessibleToUser ) {
						subjectsRemaining--;
					}
				}
			}
		}

		public static IEnumerable<WrappedRdfTag> GetTags( object subject ) {
			Uri subjectUri = GetUri( subject );
			return uriTags.ContainsKey( subjectUri ) ? (IEnumerable<WrappedRdfTag>) uriTags[subjectUri] : (IEnumerable<WrappedRdfTag>) emptyTags;
		}

		private static void CreateOntologyTagTemplates() {
			string methodName = "CreateOntologyTemplates";
			WriteLine( "{0}: {1} starting", DateTime.Now, methodName );

			RdfTagServiceSoapClient s = GetSoapClient();

			SimpleProgressWindow progress = ShowProgressWindow( "Loading ontology tags..." );
			
			s.GetObjectsCompleted += delegate( object sender, GetObjectsCompletedEventArgs args ) {
				HideProgressWindow( progress );
				
				if ( AllOk( "CreateOntologyTemplates", args ) ) {
					WriteLine( "{0}: {1} data received", DateTime.Now, methodName );

					foreach ( SubjectPredicateObject triple in args.Result ) {
						string subjectNamespace = NamespaceOf( triple.SubjectUri );

						if ( ontologyNamespacePredicates.ContainsKey( subjectNamespace )
						&& ontologyNamespacePredicates[subjectNamespace] == triple.PredicateUri
						) {
							EnsureCategorisedTemplateExists( triple.ObjectResource, PredicateTermMeans, triple.SubjectUri );
						}
					}

					OntologyTemplatesLoaded = true;

					if ( WhenOntologyTemplatesLoaded != null ) {
						WhenOntologyTemplatesLoaded();
					}

					WriteLine( "{0}: {1} finished", DateTime.Now, methodName );
				}
			};

			try {
				s.GetObjectsAsync( ( from tag in allTagsAccessibleToUser
									 where tag.TagPredicateUri == PredicateUsefulForTaggingType
									 select new SubjectPredicate {
										 Subject = tag.TagSubjectUri,
										 Predicate = ontologyNamespacePredicates[NamespaceOf( tag.TagSubjectUri )]
									 } ).ToArray() );
			}
			catch {
				// Guard ourselves against spurious tags, but ignore them.
			}
		}

		private static void CreateUserTagTemplates() {
			const string Anonymous = "http://purl.org/net/terms#AnonymousUser";

			foreach ( RdfTag tag in allTagsAccessibleToUser ) {
				CategorisedRdfTagTemplate template = EnsureCategorisedTemplateExists(
					tag.TagLabelName, tag.TagPredicateUri, tag.TagObjectUri
				);

				if ( tag.UserIdentifier == Anonymous ) {
					template.IsAnonymous = true;
				}
				else if ( tag.UserIdentifier == authenticatedUserName ) {
					template.IsMine = true;
				}
				else {
					template.IsFriend = true;
				}
			}
		}

		public static CategorisedRdfTagTemplate CreateTemplateForUser( string label ) {
			CategorisedRdfTagTemplate template = EnsureCategorisedTemplateExists(
				label, "", ""
			);

			template.IsMine = true;

			return template;
		}

		public static string NamespaceOf( string uri ) {
			string[] parts = uri.Split( new char[] { '/', ':' }, StringSplitOptions.RemoveEmptyEntries );
			return parts.Length > 1 ? parts[parts.Length - 2] : "";
		}

		public static string IdOf( string uri ) {
			string[] parts = uri.Split( new char[] { '/', ':' }, StringSplitOptions.RemoveEmptyEntries );
			return parts.Length > 0 ? parts[parts.Length - 1] : "";
		}

		public static CategorisedRdfTagTemplate EnsureCategorisedTemplateExists(
			string tagLabel,
			string predicateUri,
			string objectUri
		) {
			List<CategorisedRdfTagTemplate> templates = null;

			if ( !tagTemplates.ContainsKey( tagLabel ) ) {
				tagTemplates.Add( tagLabel, templates = new List<CategorisedRdfTagTemplate>() );
			}

			else {
				templates = tagTemplates[tagLabel];
			}

			var matchingTemplates = from t in templates
									where t.Matches( tagLabel, predicateUri, objectUri )
									select t;

			if ( matchingTemplates.Any() ) {
				return matchingTemplates.First();
			}
			else {
				var template = new CategorisedRdfTagTemplate( new RdfTag {
					TagLabelName = tagLabel,
					TagPredicateUri = predicateUri,
					TagObjectUri = objectUri
				} );

				templates.Add( template );

				return template;
			}
		}
	}
}
